package cn.classg.redisson.aspect;

import cn.classg.redisson.annotation.Lock;
import cn.classg.redisson.bo.LockInfo;
import cn.classg.redisson.factory.LockServiceFactory;
import cn.classg.redisson.service.LockService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 分布式锁切面处理器
 * @author CLASSG
 */
@Aspect
@Component
@Order(0)
public class LockAspectHandler {

    public LockAspectHandler(LockInfoProvider lockInfoProvider, LockServiceFactory lockFactory) {
        this.lockInfoProvider = lockInfoProvider;
        this.lockFactory = lockFactory;
    }

    @Around(value = "@annotation(lock)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        currentThreadLockReleaseLock.set(false);
        // 获取锁服务
        LockService lockService = lockFactory.getLock(lock.lockType());
        // 获取锁信息
        LockInfo lockInfo = lockInfoProvider.getLockInfo(joinPoint, lock);
        // 设置锁信息
        lockService.setLockInfo(lockInfo);
        // 加锁
        boolean lockRes = lockService.lock();
        if (lockRes) {
            // 加锁成功后保存锁信息到threadlocal（后面释放锁的时候需要用到）
            currentThreadLock.set(lockService);
            // 设置需要释放锁
            currentThreadLockReleaseLock.set(true);
            // 加锁成功后放行方法执行
            return joinPoint.proceed();
        } else {
            throw new RuntimeException("Get lock failed.");
        }
    }

    @AfterReturning(value = "@annotation(lock)")
    public void afterReturning(Lock lock) {
        if (currentThreadLockReleaseLock.get()) {
            currentThreadLock.get().releaseLock();
        }
    }

    @AfterThrowing(value = "@annotation(lock)")
    public void afterThrowing(Lock lock) {
        if (currentThreadLockReleaseLock.get()) {
            currentThreadLock.get().releaseLock();
        }
    }

    private final LockInfoProvider lockInfoProvider;

    private final LockServiceFactory lockFactory;

    private final ThreadLocal<LockService> currentThreadLock = new ThreadLocal<>();

    private final ThreadLocal<Boolean> currentThreadLockReleaseLock = new ThreadLocal<>();
}
